package dovs;

import dovs.node.*;

import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;

import java.io.*;
import java.util.*;

public class ClassFileParser {
	private static int dummy_name_counter = 0;

	private JavaClass cl;

	public ClassFileParser(String filename) throws IOException {
		cl = new ClassParser(filename).parse();
	}

	public ClassFileParser(String jar, String filename) throws IOException {
		cl = new ClassParser(jar, filename).parse();
	}

	@SuppressWarnings("serial")
	public static class PrivateException extends Exception {
		private PTypeDecl type_decl;

		PrivateException(PTypeDecl type_decl) {
			this.type_decl = type_decl;
		}

		public PTypeDecl getTypeDecl() {
			return type_decl;
		}
	}

	public PTypeDecl makeTypeDecl() throws PrivateException {
		PTypeDecl type_decl;
		if (cl.isClass()) {
			type_decl = makeClass();
		} else {
			type_decl = makeInterface();
		}
		if (!cl.isPublic()) {
			throw new PrivateException(type_decl);
		}
		return type_decl;
	}

	private PTypeDecl makeClass() {
		TFinal tfinal = cl.isFinal() ? new TFinal() : null;
		TAbstract tabstract = cl.isAbstract() ? new TAbstract() : null;
		TIdentifier name = makeIdentifier(cl.getClassName());
		String super_class_name = cl.getSuperclassName();
		ANamedType super_class = cl.getClassName().equals("java.lang.Object") ? null
				: makeType(super_class_name);
		List<ANamedType> super_interfaces = new ArrayList<ANamedType>();
		String[] super_interface_names = cl.getInterfaceNames();
		if (super_interface_names != null) {
			for (String element : super_interface_names) {
				super_interfaces.add(makeType(element));
			}
		}

		List<PDecl> members = makeMembers();

		return makeEnv(new AClassTypeDecl(tfinal, tabstract, name, super_class,
				super_interfaces, members));
	}

	private PTypeDecl makeInterface() {
		TIdentifier name = makeIdentifier(cl.getClassName());
		List<ANamedType> super_interfaces = new ArrayList<ANamedType>();
		String[] super_interface_names = cl.getInterfaceNames();
		if (super_interface_names != null) {
			for (String element : super_interface_names) {
				super_interfaces.add(makeType(element));
			}
		}
		List<PDecl> members = makeMembers();

		return makeEnv(new AInterfaceTypeDecl(name, super_interfaces, members));
	}

	private PTypeDecl makeEnv(PTypeDecl type_decl) {
		if (type_decl != null) {
			for (PDecl decl : type_decl.getMembers()) {
				if (decl instanceof AMethodDecl) {
					String name = ((AMethodDecl) decl).getName().getText();
					if (!type_decl.method_env.containsKey(name)) {
						type_decl.method_env.put(name,
								new HashSet<AMethodDecl>());
					}
					type_decl.method_env.get(name).add((AMethodDecl) decl);
				} else if (decl instanceof AFieldDecl) {
					type_decl.field_env.put(
							((AFieldDecl) decl).getName().getText(),
							(AFieldDecl) decl);
				}
			}
		}
		return type_decl;
	}

	private List<PDecl> makeMembers() {
		List<PDecl> members = new ArrayList<PDecl>();
		for (Method m : cl.getMethods()) {
			PDecl mm = makeMethod(m);
			if (mm != null) {
				members.add(mm);
			}
		}
		ConstantPool cp = cl.getConstantPool();
		for (Field f : cl.getFields()) {
			AFieldDecl fm = makeField(f);
			if (fm != null) {
				members.add(fm);
				// Get ConstantValue attribute
				for (Attribute a : f.getAttributes()) {
					if (a instanceof ConstantValue) {
						ConstantValue cv = (ConstantValue) a;
						Constant c = cp.getConstant(cv.getConstantValueIndex());
						switch (fm.getType().kindPType()) {
						case BYTE:
						case SHORT:
						case INT:
						case CHAR:
							int intvalue = (Integer) ((ConstantInteger) c).getConstantValue(cp);
							fm.setInit(Util.makeIntConst(intvalue));
							break;
						case BOOLEAN:
							boolean boolvalue = (Integer) ((ConstantInteger) c).getConstantValue(cp) != 0;
							fm.setInit(Util.makeBooleanConst(boolvalue));
							break;
						case NAMED:
							if (((ANamedType) fm.getType()).getName().nameText().equals(
									"java.lang.String")) {
								String stringvalue = (String) ((ConstantString) c).getConstantValue(cp);
								fm.setInit(Util.makeStringConst(stringvalue));
							}
							break;
						default:
							// no action
						}
					}
				}
			}
		}
		return members;
	}

	private PDecl makeMethod(Method m) {
		PAccess access;
		if (m.isPublic()) {
			access = new APublicAccess();
		} else if (m.isProtected()) {
			access = new AProtectedAccess();
		} else {
			return null;
		}

		List<ALocalDecl> formals = new ArrayList<ALocalDecl>();
		for (Type at : m.getArgumentTypes()) {
			formals.add(new ALocalDecl(makeType(at), makeDummyName(), null));
		}
		List<ANamedType> exceptions = new ArrayList<ANamedType>();
		if (m.getExceptionTable() != null) {
			for (String ename : m.getExceptionTable().getExceptionNames()) {
				exceptions.add(makeType(ename));
			}
		}

		if (m.getName().equals("<init>")) {
			// Constructor
			TIdentifier c_name = makeIdentifier(cl.getClassName());
			return new AConstructorDecl(access, c_name, formals, exceptions,
					null);
		} else if (m.getName().equals("<clinit>")) {
			// Static initializer
			return null;
		} else {
			// Ordinary method
			TStatic tstatic = m.isStatic() ? new TStatic() : null;
			TFinal tfinal = m.isFinal() ? new TFinal() : null;
			TAbstract tabstract = m.isAbstract() ? new TAbstract() : null;
			PType rtype = makeType(m.getReturnType());
			TIdentifier name = makeIdentifier(m.getName());

			return new AMethodDecl(access, tstatic, tfinal, tabstract, rtype,
					name, formals, exceptions, null);
		}
	}

	private AFieldDecl makeField(Field f) {
		PAccess access;
		if (f.isPublic()) {
			access = new APublicAccess();
		} else if (f.isProtected()) {
			access = new AProtectedAccess();
		} else {
			return null;
		}

		TStatic tstatic = f.isStatic() ? new TStatic() : null;
		TFinal tfinal = f.isFinal() ? new TFinal() : null;
		PType type = makeType(f.getType());
		TIdentifier name = makeIdentifier(f.getName());

		return new AFieldDecl(access, tstatic, tfinal, type, name, null);
	}

	private AQualifiedName makeName(String full_name) {
		List<TIdentifier> names = new ArrayList<TIdentifier>();
		StringTokenizer st = new StringTokenizer(full_name, ".");
		while (st.hasMoreTokens()) {
			String name = st.nextToken();
			names.add(new TIdentifier(name));
		}
		return new AQualifiedName(names);
	}

	private TIdentifier makeIdentifier(String full_name) {
		String id = full_name.substring(full_name.lastIndexOf('.') + 1,
				full_name.length());
		return new TIdentifier(id);
	}

	private PType makeType(Type t) {
		if (t == Type.BOOLEAN) {
			return new ABooleanType();
		}
		if (t == Type.BYTE) {
			return new AByteType();
		}
		if (t == Type.CHAR) {
			return new ACharType();
		}
		if (t == Type.DOUBLE) {
			return new ADoubleType();
		}
		if (t == Type.FLOAT) {
			return new AFloatType();
		}
		if (t == Type.INT) {
			return new AIntType();
		}
		if (t == Type.LONG) {
			return new ALongType();
		}
		if (t == Type.SHORT) {
			return new AShortType();
		}
		if (t == Type.VOID) {
			return new AVoidType();
		}
		if (t instanceof ArrayType) {
			Type et = ((ArrayType) t).getElementType();
			return new AArrayType(null, makeType(et));
		}
		if (t instanceof ObjectType) {
			return makeType(((ObjectType) t).getClassName());
		}
		throw new InternalCompilerError("Unknown BCEL type: " + t);
	}

	private ANamedType makeType(String full_name) {
		return new ANamedType(makeName(full_name));
	}

	private TIdentifier makeDummyName() {
		return new TIdentifier("dummy$" + (++dummy_name_counter));
	}

}
